#include "spatialFilter.h"
#include <algorithm>
#include <cmath>
#include <vector>

SpatialFilter::SpatialFilter() : Filter(){
}

SpatialFilter::SpatialFilter(Data *_data) : Filter(_data){
}

SpatialFilter::~SpatialFilter(){
}

void SpatialFilter::grayScaleFilter(){
    unsigned char grey;

    Color color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = data->getData(i, j);
            grey = (0.212671 * (int)color.red + 0.715160 * (int)color.green + 0.072169 * (int)color.blue);

            data->setData(i, j, Color(grey, grey, grey));
        }
    }
}

void SpatialFilter::negativeFilter(){
    Color color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = data->getData(i, j);
            color = 255 - color;

            data->setData(i, j, color);
        }
    }
}

void SpatialFilter::logFilter(double _c){
    double x0 = 0;//log(1);
    double xn = _c * log(256);
    double y0 = 0;
    double yn = 255;

    Color color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = data->getData(i, j);
            color.red = normalize(x0, xn, y0, yn,_c * log((int)color.red + 1));
            color.green = normalize(x0, xn, y0, yn,_c * log((int)color.green + 1));
            color.blue = normalize(x0, xn, y0, yn, _c * log((int)color.blue + 1));

            data->setData(i, j, color);
        }
    }
}

void SpatialFilter::powerFilter(double _c, double _gamma){
    double x0 = 0;//log(1);
    double xn = _c * pow(255, _gamma);
    double y0 = 0;
    double yn = 255;

    Color color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = data->getData(i, j);
            color.red = normalize(x0, xn, y0, yn, _c * pow(color.red, _gamma));
            color.green = normalize(x0, xn, y0, yn, _c * pow(color.green, _gamma));
            color.blue = normalize(x0, xn, y0, yn, _c * pow(color.blue, _gamma));

            data->setData(i, j, color);
        }
    }
}

void SpatialFilter::thresholdingFilter(int _threshold){
    grayScaleFilter();

    Color color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = data->getData(i, j);
            if (color.red <= _threshold) data->setData(i, j, Color(0, 0, 0, 255));
            else data->setData(i, j, Color(255, 255, 255, 255));
        }
    }
}

int *SpatialFilter::equalizeHistogram(Histogram *_histogram){
    int *s = new int[256];
    double sum = 0.0;

    for(int i = 0; i < 256; i++){
        sum = 0;
        for(int j = 0; j < i + 1; j++){
            sum += _histogram->getP(j);
        }
        s[i] = floor(255 * sum);
    }

    return s;
}

void SpatialFilter::histogramEqualization(Data * _data){
    if(!_data) _data = data;
    Histogram histogram(_data);

    int *s = equalizeHistogram(&histogram);


    unsigned char color;
    for(int i = 0; i < _data->getWidth(); i++){
        for(int j = 0; j < _data->getHeight(); j++){
            color = s[_data->getData(i, j).red];
            _data->setData(i, j, Color(color, color, color));
        }
    }

    delete s;
}

void SpatialFilter::histogramEqualizationFilter(){
    grayScaleFilter();
    histogramEqualization();
}

void SpatialFilter::histogramMatchingFilter(Histogram _specified){
    //o histograma dado eh é corerto, isto é somatiorio = 1
    grayScaleFilter();
    Histogram histogram(data);

    int *s = equalizeHistogram(&histogram);
    int *z = equalizeHistogram(&_specified);
    int r[256];


    int min = 0;
    int dMin = 256;
    int d;
    for(int i = 0; i < 256; i++){
        min = 0;
        dMin = 255;
        for(int j = 0; j < 256; j++){
            if(z[j] - dMin > s[i]) break;

            d = fabs(s[i] - z[j]);
            if (d < dMin){
                dMin = d;
                min = j;
            }
        }
        r[i] = min;
    }


    unsigned char color;
    for(int i = 0; i < data->getWidth(); i++){
        for(int j = 0; j < data->getHeight(); j++){
            color = r[data->getData(i, j).red];
            data->setData(i, j, Color(color, color, color));
        }
    }

    delete s;
    delete z;
}

Data *SpatialFilter::localHistogramProcessingFilter(Mask *_mask){
    grayScaleFilter();

    data = padding(_mask->getN() - 1, _mask->getM() - 1);

    Data *newData = new Data(data->getWidth(), data->getHeight());
    Data maskData = Data(_mask->getM(), _mask->getN());
    unsigned char maskDataChar[_mask->getM() * _mask->getN() * 4];
    int k = 0;

    for(int i = _mask->getA(); i < data->getWidth() - _mask->getA(); i++){
        for(int j = _mask->getB(); j < data->getHeight() - _mask->getB(); j++){

            k = 0;
            for(int s = -_mask->getB(); s <= _mask->getB(); s++){
                for(int r = -_mask->getA(); r <= _mask->getA(); r++){

                    maskDataChar[k++] = data->getData(i + r, j + s).red;
                    maskDataChar[k++] = data->getData(i + r, j + s).green;
                    maskDataChar[k++] = data->getData(i + r, j + s).blue;
                    maskDataChar[k++] = data->getData(i + r, j + s).alpha;
                }
            }
            maskData.setData(maskDataChar);
            histogramEqualization(&maskData);

            newData->setData(i, j, maskData.getData(_mask->getA(), _mask->getB()));

        }
    }

    delete data;
    data = newData;
    data = cropping(_mask->getN() - 1, _mask->getM() - 1);
    return data;
}

Data *SpatialFilter::localHistogramProcessingWithStatisticsFilter(Mask *_mask, double _k0, double _k1, double _k2, double _e){
    grayScaleFilter();

    data = padding(_mask->getN() - 1, _mask->getM() - 1);
    Data *newData = new Data(data->getWidth(), data->getHeight());
    Data maskData = Data(_mask->getM(), _mask->getN());
    unsigned char maskDataChar[_mask->getM() * _mask->getN() * 4];
    int k = 0;
    Histogram *histogramMask;
    Histogram histogram(data);
    double meanG = histogram.getMean();
    double sqrtVarianceG = sqrt(histogram.getVariance());


    double meanMask, sqrtVarianceMask;
    Color color;

    for(int i = _mask->getA(); i < data->getWidth() - _mask->getA() -1; i++){
        for(int j = _mask->getB(); j < data->getHeight() - _mask->getB() -1; j++){

            k = 0;
            for(int s = -_mask->getB(); s <= _mask->getB(); s++){
                for(int r = -_mask->getA(); r <= _mask->getA(); r++){

                    maskDataChar[k++] = data->getData(i + r, j + s).red;
                    maskDataChar[k++] = data->getData(i + r, j + s).green;
                    maskDataChar[k++] = data->getData(i + r, j + s).blue;
                    maskDataChar[k++] = data->getData(i + r, j + s).alpha;
                }
            }
            maskData.setData(maskDataChar);
            histogramMask = new Histogram(&maskData);
            meanMask = histogramMask->getMean();
            sqrtVarianceMask = sqrt(histogramMask->getVariance());




            if( (meanMask <= _k0 * meanG) && (_k1 * sqrtVarianceG <= sqrtVarianceMask) && (sqrtVarianceMask <= _k2 * sqrtVarianceG)){
                color = maskData.getData(_mask->getA(), _mask->getB());

                color = _e * color;
                color.alpha = 255;
                newData->setData(i, j, color);
            } else
                newData->setData(i, j, maskData.getData(_mask->getA(), _mask->getB()));

            delete histogramMask;
        }
    }

    delete data;
    data = newData;
    data = cropping(_mask->getN() - 1, _mask->getM() - 1);
    return data;
}

Data *SpatialFilter::correlation(Mask *_mask, Data *_data){
    if(!_data) _data = data;

    _data = padding(_mask->getN() - 1, _mask->getM() - 1, _data);

    Data *newData = new Data(_data->getWidth(), _data->getHeight());
    double red = 0, green = 0, blue = 0, weight = 1;
    Color color;

    for(int i = _mask->getA(); i < _data->getWidth() - _mask->getA() -1; i++){
        for(int j = _mask->getB(); j < _data->getHeight() - _mask->getB() -1; j++){

            red = green = blue = weight = 0.0;
            for(int s = -_mask->getB(); s <= _mask->getB(); s++){
                for(int r = -_mask->getA(); r <= _mask->getA(); r++){
                    red += _mask->getW(r, s) * (double)_data->getData(i + r, j + s).red;
                    green += _mask->getW(r, s) * (double)_data->getData(i + r, j + s).green;
                    blue += _mask->getW(r, s) * (double)_data->getData(i + r, j + s).blue;
                    weight += _mask->getW(r, s);
                }
            }

            if(weight == 0.0) weight = 1.0;
            color = Color(red/weight, green/weight, blue/weight);
            newData->setData(i, j, color);
        }
    }

    delete _data;
    _data = cropping(_mask->getN() - 1, _mask->getM() - 1, newData);
    return _data;
}

Data *SpatialFilter::convolution(Mask *_mask, Data *_data){
    if(!_data) _data = data;

    _data = padding(_mask->getN() - 1, _mask->getM() - 1, _data);


    Data *newData = new Data(_data->getWidth(), _data->getHeight());
    double red = 0, green = 0, blue = 0, weight = 0;
    Color color;

    for(int i = _mask->getA(); i < data->getWidth() - _mask->getA() -1; i++){
        for(int j = _mask->getB(); j < data->getHeight() - _mask->getB() -1; j++){

            red = green = blue = weight = 0.0;
            for(int s = -_mask->getB(); s <= _mask->getB(); s++){
                for(int r = -_mask->getA(); r <= _mask->getA(); r++){
                    red += _mask->getW(r, s) * (double)_data->getData(i - r, j - s).red;
                    green += _mask->getW(r, s) * (double)_data->getData(i - r, j - s).green;
                    blue += _mask->getW(r, s) * (double)_data->getData(i - r, j - s).blue;
                    weight += _mask->getW(r, s);
                }
            }

            if(weight == 0.0) weight = 1.0;
            color = Color(red/weight, green/weight, blue/weight);
            newData->setData(i, j, color);
        }
    }

    delete _data;
    _data = cropping(_mask->getN() - 1, _mask->getM() - 1, newData);
    return _data;
}

Data *SpatialFilter::orderStaticFilter(Mask *_mask, int _position){
    grayScaleFilter();
    std::vector<double> vector;

    data = padding(_mask->getN() - 1, _mask->getM() - 1);
    Data *newData = new Data(data->getWidth(), data->getHeight());
    unsigned char color;


    for(int i = _mask->getA(); i < data->getWidth() - _mask->getA(); i++){
        for(int j = _mask->getB(); j < data->getHeight() - _mask->getB(); j++){

            for(int s = -_mask->getB(); s <= _mask->getB(); s++){
                for(int r = -_mask->getA(); r <= _mask->getA(); r++){
                    vector.push_back((double)data->getData(i - r, j - s).red);
                }
            }
            std::sort( vector.begin(), vector.end() );
            color = vector.at( _position );

            newData->setData(i, j, Color(color, color, color));
            vector.clear();
        }
    }

    delete data;
    data = newData;
    data = cropping(_mask->getN() - 1, _mask->getM() - 1);
    return data;
}

Data *SpatialFilter::unsharpMaskingAndHighboostFilter(Mask *_mask){
    grayScaleFilter();

    Data *blurData = correlation(_mask, new Data(*data));
    int *unsharpMask = subtractInt(*data, *blurData);

    Data *result = add(*data, unsharpMask);

    delete blurData;
    delete unsharpMask;
    delete data;

    data = result;
    return data;
}

Data *SpatialFilter::robertFilter(){
    grayScaleFilter();

    Mask mask(3,3);

    data = padding(mask.getN() - 1, mask.getM() - 1);

    Data *newData = new Data(data->getWidth(), data->getHeight());
    double color[data->getWidth() * data->getWidth()], max = 0;
    int gx, gy;
    int z5, z6, z8, z9;

    int k = 0;
    for(int i = mask.getA(); i < data->getWidth() - mask.getA(); i++){
        for(int j = mask.getB(); j < data->getHeight() - mask.getB(); j++){
            z5 = (int)data->getData(i, j).red;
            z6 = (int)data->getData(i, j + 1).red;
            z8 = (int)data->getData(i + 1, j).red;
            z9 = (int)data->getData(i + 1, j + 1).red;

            gx = z9 - z5;
            gy = z8 - z6;
            color[k] = (fabs(gx) + fabs(gy));

            if(color[k] > max) max = color[k];
            k++;
        }
    }

    double x0 = 0;
    double xn = max;
    double y0 = 0;
    double yn = 255;
    k = 0;
    for(int i = mask.getA(); i < data->getWidth() - mask.getA(); i++){
        for(int j = mask.getB(); j < data->getHeight() - mask.getB(); j++){

            color[k] = normalize(x0, xn, y0, yn, color[k]);
            newData->setData(i, j, Color((double)color[k], (double)color[k], (double)color[k]));
            k++;
        }
    }

    delete data;
    data = newData;
    data = cropping(mask.getN() - 1, mask.getM() - 1);
    return data;
}

Data *SpatialFilter::sobelFilter(){
    grayScaleFilter();

    Mask mask(3,3);

    data = padding(mask.getN() - 1, mask.getM() - 1);

    Data *newData = new Data(data->getWidth(), data->getHeight());
    double color[data->getWidth() * data->getWidth()], max = 0;
    int gx, gy;
    int z1, z2, z3, z4, z6, z7, z8, z9;

    int k = 0;
    for(int i = mask.getA(); i < data->getWidth() - mask.getA(); i++){
        for(int j = mask.getB(); j < data->getHeight() - mask.getB(); j++){
            z1 = (int)data->getData(i - 1, j - 1).red;
            z2 = (int)data->getData(i - 1, j).red;
            z3 = (int)data->getData(i - 1, j + 1).red;
            z4 = (int)data->getData(i, j - 1).red;
            z6 = (int)data->getData(i, j + 1).red;
            z7 = (int)data->getData(i + 1, j - 1).red;
            z8 = (int)data->getData(i + 1, j).red;
            z9 = (int)data->getData(i + 1, j + 1).red;

            gx = (z7 + (2*z8) + z9) - (z1 + (2*z2) + z3);
            gy = (z3 + (2*z6) + z9) - (z1 + (2*z4) + z7);
            color[k] = (fabs(gx) + fabs(gy));

            if(color[k] > max) max = color[k];
            k++;
        }
    }


    double x0 = 0;
    double xn = max;
    double y0 = 0;
    double yn = 255;
    k = 0;
    for(int i = mask.getA(); i < data->getWidth() - mask.getA(); i++){
        for(int j = mask.getB(); j < data->getHeight() - mask.getB(); j++){

            color[k] = normalize(x0, xn, y0, yn, color[k]);
            newData->setData(i, j, Color((double)color[k], (double)color[k], (double)color[k]));
            k++;
        }
    }

    delete data;
    data = newData;
    data = cropping(mask.getN() - 1, mask.getM() - 1);
    return data;
}
